#ifndef UNIVERSAL_OBJECT_H
#define UNIVERSAL_OBJECT_H

//-----------------------------------------------------------------------------

#include <functional>
#include <vector>
#include <algorithm>
#include <omp.h>
#include "../consensus/cas_consensus.h"

//-----------------------------------------------------------------------------

template <class Obj>
using Invoc = std::function<void (Obj*)>;

//-----------------------------------------------------------------------------

template <class Obj>
struct Node {
	
	Invoc<Obj> invoc;
	CasConsensus<Node<Obj>*> decide_next;
	size_t seq;
	Node<Obj> *pnext;
	
	Node<Obj>(Invoc<Obj> inv, size_t num_threads): 
		invoc(inv),
		decide_next(num_threads),
		seq(0),
		pnext(nullptr) {}
};

//-----------------------------------------------------------------------------

template <class Obj>
Node<Obj> *MaxNode(Node<Obj> **nodes_arr, size_t size) {
	Node<Obj> *ret = nodes_arr[0];
	for (size_t i = 1; i <size; ++i) 
		if (ret->seq < nodes_arr[i]->seq)
			ret = nodes_arr[i];
	return ret;
}

//-----------------------------------------------------------------------------

template <class Obj>
class Universal {
	std::vector<Node<Obj>*> head_;
	Node<Obj> *tail_;
	size_t num_threads_;

    public:
	Universal(size_t num_threads) {
		num_threads_ = num_threads;
		tail_ = new Node<Obj>(nullptr, num_threads);	
		tail_->seq = 1;
		for (size_t i = 0; i < num_threads; ++i)
			head_.push_back(tail_);
	}
	~Universal() {
		Node<Obj> *current = tail_;
		while (current) {
			Node<Obj> *old = current;
			current = current->pnext;
			delete old;
		}
	}
	
	void Apply(Invoc<Obj> invoc) {
		size_t i = omp_get_thread_num();
		Node<Obj> *prefer = new Node<Obj>(invoc, num_threads_);
		while (!prefer->seq) {
			Node<Obj> *before = MaxNode(head_.data(), head_.size());
			before->decide_next.Propose(prefer);
			Node<Obj> *after = before->decide_next.Decide();
			before->pnext = after;
			after->seq = before->seq + 1;
			head_[i] = after;
		}
		
	}

	
	void Commit(Obj *pobj) {
		assert(tail_);
		Node<Obj> *current = tail_->pnext;
		size_t i = omp_get_thread_num();
		while (current != head_[i]) {
			assert(current);
			current->invoc(pobj);
			current = current->pnext;
		}
		current->invoc(pobj);
	}


};

//-----------------------------------------------------------------------------

#endif // UNIVERSAL_OBJECT_H
